<?php

/**
 * 基于PDO的数据库类
 * @author zhoupeng 2012.3.27
 * 
 * 
 *
 */
class database_mysql extends PDO
{
	/**
	 * 数据库连接配置(包括用户名、密码)
	 *
	 * @var array
	 */
	public $options = array ();
	/* 		'cis' => array(
			'type' => 'mysql',
			'host' => 'localhost',
			'port' => '3306',
			'database' => 'winhidb_news',
			'user' => 'root',
			'password' => 'sa',
			'prefix' => 'winhi_',
			'charset' => 'utf8',
			'error_input' => true
		) */
		
	/**
	 * 设置模块
	 *
	 * @var array
	 */
	public $attributes = array (
		PDO :: ATTR_CASE => PDO :: CASE_NATURAL, //PDO::CASE_LOWER,PDO::CASE_NATURAL,PDO::CASE_UPPER
		PDO :: ATTR_ERRMODE => PDO :: ERRMODE_WARNING, //PDO::ERRMODE_SILENT(仅设置出错代码),PDO::ERRMODE_WARNING(E_WARRNING),PDO::ERRMODE_EXCEPTION(抛出异常)
		PDO :: ATTR_ORACLE_NULLS => PDO :: NULL_NATURAL, //PDO::NULL_NATURAL(不转换),PDO::NULL_EMPTY_STRING(Empty string is converted to NULL),NULL_TO_STRING(NULL to empty string)
		PDO :: ATTR_STRINGIFY_FETCHES => false,
		PDO :: ATTR_STATEMENT_CLASS => '', //用于自定义PDOStatement类,
		PDO :: ATTR_AUTOCOMMIT => true, //用于OCI,Firebird,MySQL自动提交
		PDO :: MYSQL_ATTR_USE_BUFFERED_QUERY => true, //用于MySQL buffer
	);
	/**
	 * PDO支持的常量属性简写
	 * $conn->getAttribute(constant("PDO::ATTR_$val")) 
	 */
	private $_attributes = array (
		'CASE',
		'ERRMODE',
		'ORACLE_NULLS',
		'AUTOCOMMIT',
		'CLIENT_VERSION',
		'CONNECTION_STATUS',
		'PERSISTENT',
		'PREFETCH',
		'SERVER_INFO',
		'SERVER_VERSION',
		'TIMEOUT'
	);
	/**
	 * 设置取出结果模式
	 * 2 PDO::FETCH_ASSOC -- 关联数组形式
	 * 3 PDO::FETCH_NUM   -- 数字索引数组形式
	 * 4 PDO::FETCH_BOTH  -- 两者数组形式都有，这是缺省的
	 * 5 PDO::FETCH_OBJ   -- 按照对象的形式，类似于以前的 mysql_fetch_object()
	 *
	 * @var string
	 * @access private
	 */
	private $fetchMode = PDO :: FETCH_ASSOC;
	/**
     * 事务嵌套层数
     *
     * @var int
     */
    protected $transactionNestingLevel = 0;
    
    /**
     * 如果sql查询失败,则该标志为true,事务应该回滚.
     *
     * @var bool
     */
    protected $transactionErrorFlag = false;
	/**
	 * 实体引用符号
	 * 
	 */
	protected $nameQuote='';

	/**
	 * 保存之前执行的$pdo->prepare($sql);
	 * 
	 */
	public $prepared=array();
	/**
	 * 是否调试
	 *
	 * @var boolean
	 * @access private
	 *
	 */
	public $debug = 1;
	/**
	 * 记录查询历史
	 *
	 * @var array
	 */
	private $history = array ();
	/**
	 * 保存数据库查询次数
	 *
	 * @var integer
	 * @access private
	 */
	public $numExecutes = 0;
	/**
	 * 保存数据库statements次数
	 * @var integer
	 * @access  private
	 */
	public $numStatements = 0;
	/**
	 * 记录查询历史,保存上一查询状态: 查询结果类型(boolen,source),插入ID $insertId，
	 * 影响行数$affectedRows， 结果行数$numRows
	 *
	 * @var array
	 * @access private
	 */
	public $stack=array();
	
	/**
	 * 配置文件中使用的数据库标志
	 *
	 * @var string
	 */
	//public $tag;
	public $error;
	

	
	/**
	 * 构造函数
	 *
	 * @param array $options
	 */
	public function __construct($options)
	{
		
	
		///Debug::show($options['type']);
		//$this->tag=$tag;
		$this->options =$options;
		$dsn = sprintf("%s:host=%s;dbname=%s", $options['type'], $options['host'], $options['database']);
		try
		{
			parent :: __construct($dsn, $options['user'], $options['password'], array (
				//PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES '{$options['charset']}';",
				PDO :: ATTR_PERSISTENT => '0',
			));
			$this->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
		}
		catch (PDOException $e)
		{
			 
			
				trigger_error($e->getMessage(), E_USER_ERROR);
			   //debug::show($e->getMessage());
				//var_dump($e->getMessage());
				 
			// $this->showErrorInfo('Sql Connect Error!');
			 
			 //die('Sql Connect Error!');
		 	//throw $e;
			// 
			//
	 
		}
		switch ($this->getAttribute(PDO::ATTR_DRIVER_NAME)) {
	      case 'mysql':
	        $this->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, TRUE);
	        $this->nameQuote='`';
	        $this->setAttribute(PDO :: ATTR_STATEMENT_CLASS, array ('DBStatement',array ($this)));
	        $this->exec("SET NAMES '{$options['charset']}';");
	        break;
	      case 'mssql':
	        $this->nameQuote = '[]';
	        $this->setAttribute(PDO :: ATTR_STATEMENT_CLASS, array ('DBStatement',array ($this)));
	        break;
	      case 'sqlite':
	        $this->sqliteCreateAggregate(
	          "group_concat",
	          Array($this, '__sqlite_group_concat_step'),
	          Array($this, '__sqlite_group_concat_finalize'),
	          2
	        );
	        $this->nameQuote = $this->nameClosing = '';
	        $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, Array('pdoext_SQLiteStatement'));
	      default:
	        $this->nameQuote= '"';
	        $this->setAttribute(PDO :: ATTR_STATEMENT_CLASS, array ('DBStatement',array ($this)));
	        break;
	    }
	}

 
	 
	/**
	 * 将sql语句中的`#__替换为表前缀
	 * @param string $sql sql语句,表名为`#__table`格式
	 */
	public function fixPrefix(& $sql)
	{
		if($this->nameQuote)
		{
			$sql = str_replace($this->nameQuote{0}.'#__', $this->nameQuote{0}.$this->options['prefix'], $sql);
		}else{
			$sql = str_replace('#__', $this->options['prefix'], $sql);
		}
	}
	/**
	 * prepare sql
	 *
	 * @param string $sql
	 * @return DBStatement
	 */
	public function & prepare($sql)
	{
		$hash = hash('crc32', $sql);
        if (!array_key_exists($hash, $this->prepared))
        {
        	$this->fixPrefix($sql);
        	$this->prepared[$hash] = parent::prepare($sql);
        }
        $this->stack($sql,'s');
        if(!$this->prepared[$hash])
        {
        	$this->error=$this->errorInfo();
        	return false;
        }else{
        	return $this->prepared[$hash];
        }
	}
	/**
	 * DBStatement
	 *
	 * @param string $sql 要执行的sql查询语句
	 * @return integer　有效行数,判断执行错误，可以用  exec() === false
	 */
	public function & exec($sql)
	{
		$this->fixPrefix($sql);
		$rs = parent :: exec($sql);
		$this->stack($sql,'e');
		if ($rs === false)
		{ 
			//trigger_error($err, E_USER_ERROR);
			$this->showErrorInfo();
			return false;
		}
		else
		{
			return $rs;
		}
	}
	/**
	 * 执行一个查询
	 *
	 * @param string $sql
	 * @return DBStatement
	 */
	public function query($sql,$return_statement=false)
	{
		$this->stack($sql,'s');
		$this->fixPrefix($sql);
		
		try{ 
			$rs=parent :: query($sql);
			if ($rs === false)
			{ 
				//trigger_error($err, E_USER_ERROR); 
			  $this->showErrorInfo();
				return false;
			}
			else
			{
				if($return_statement !== false){
					return $rs;
				}else{
					# 如果是插入要返回插入的自增ID
					if(strpos($sql,'insert into')!==false){
						return $this->lastInsertId();
					}else{
						return true;
					}
				}
			}
		}catch(PDOException $e){
			$this->showErrorInfo();
			return false;
		}
		
	}
	/**
	 * 得到错误信息，序列化好的
	 * 
	 */
	public function showErrorInfo($err = ''){
		if($err == ''){
			$err = $this->errorInfo();
			if ($err)
				$err = $err[2] . '. (' . $err[0] . ':' . $err[1] . ')';
			else
				$err = 'unkonw PDO exec error!';
		}
		if(isset($this->options['error_input']) && $this->options['error_input']===true){
			//die($err);	
			trigger_error($err, E_USER_ERROR);
		}else{
			trigger_error('Sql Error!', E_USER_ERROR);
			//die('Sql Error!');	
		}	
	}
	/**
	 * 保存查询结果的堆栈
	 * @param string $sql 查询语句
	 * @param string $type sql类型:s:stmt,e:exec
	 * 
	 */
	public function stack($sql,$type='s')
	{
		$this->numExecutes++;
		if($type=='s')
		{
			$this->numStatements++;
		}
		$this->stack[]=$sql;
	}
	/**
	 * 设定错误模式
	 *
	 */
	function setErrMode($mode = PDO :: ERRMODE_SILENT)
	{
		$this->setAttribute(PDO :: ATTR_ERRMODE, PDO :: ERRMODE_SILENT);
	}
	/**
	 * 设置属性集
	 * @param array $arr 属性数组
	 */
	function setAttributes($arr)
	{
		if (is_array($arr))
		{
			foreach ($arr as $key => $value)
			{
				$this->setAttribute($key, $value);
			}
		}
	}
    /**
     *	开始事务
     *
     * @see commit()
     * @see rollback()
     * @return bool
     */
    public function begin()
    {
        $retval = true;
        if ( $this->transactionNestingLevel == 0 )
        {
            $retval = parent::beginTransaction();
        }
        $this->transactionNestingLevel++;
        return $retval;
    }
    /**
     * 提交事务.所有查询中如果有一个查询出现错误,则执行rollBack.
     * 执行成功则返回trun,反之返回false.
     *
     * @see begin()
     * @see rollback()
     * @return bool
     */
    public function commit()
    {
        if ( $this->transactionNestingLevel <= 0 )
        {
            $this->transactionNestingLevel = 0;
            $this->showErrorInfo( "commit() called before beginTransaction()");
        }
        $retval = true;
        if ( $this->transactionNestingLevel == 1 )
        {
            if ( $this->transactionErrorFlag )
            {
                parent::rollback();
                $this->transactionErrorFlag = false; // reset error flag
                $retval = false;
            }
            else
            {
                parent::commit();
            }
        }
        $this->transactionNestingLevel--;
        return $retval;
    }

    /**
     * 回滚事务,该方只返回true;
     *
     * @see begin()
     * @see commit()
     * @return bool
     */
    public function rollBack()
    {
        if ( $this->transactionNestingLevel <= 0 )
        {
            $this->transactionNestingLevel = 0;
            $this->showErrorInfo( "rollBack() called without previous beginTransaction()");
        }
        if ( $this->transactionNestingLevel == 1 )//
        {
            $this->transactionErrorFlag = false;
            parent::rollBack();
        }
        else//标志为出错
        {
           $this->transactionErrorFlag or $this->transactionErrorFlag = true;
        }
        $this->transactionNestingLevel--;
        return true;
    }
    
	/**
     * 返回所有行
     *
     * @param string $sql
     * @param string $key 以该键值为下标返回
     * @param int $fetchMode 返回模式 
     * @return mix 返回为结果对象或数组PDO::FETCH_ASSOC,PDO::FETCH_NUM,PDO::FETCH_BOTH,PDO::FETCH_OBJ
     */
	public function &fetchAll($sql,$key=null,$fetchMode=PDO::FETCH_ASSOC)
	{
		$data = array();
		$stmt=$this->query($sql,true);
		if ($key)
		{
			while($row = $stmt->fetch($fetchMode))
			{
				$data[$row[$key]]=$row;
			}
		} else {
			while ($data[] = $stmt->fetch($fetchMode));
			array_pop($data);
		}
		unset($stmt);
		return $data;
	}
	function &fetch($sql,$fetchMode=PDO::FETCH_ASSOC)
	{
		$stmt = $this->query($sql,true);
		$rs=$stmt->fetch($fetchMode);
		$stmt->closeCursor();
		unset($stmt);
		return $rs;
	}
	/**
	* 取查询结果第一行的第$offset个字段
	*
	* @param string $sql 查询语句
	* @param int $offset 偏移
	* @return 查询返回结果，查询失败返回为NULL
	*/
	function fetchColumn($sql,$offset=0)
	{
		$stmt = $this->query($sql,true);
		$rs=$stmt->fetchColumn($offset);
		$stmt->closeCursor();
		return $rs;
	}
	/**
	* 取查询结果第一行的第$offset个字段
	* 
	* @param string $sql 查询语句
	* @param int $offset 偏移
	* @return 查询返回结果，查询失败返回为NULL
	*/
	function LoadResult($sql,$offset=0)
	{
		return $this->fetchColumn($sql,$offset);
	}
	/**
     * Assoc方式返回所有行
     * @param string $sql
     * @param string $key 以该键值为下标返回
     */
	public function &loadAssocList($sql,$key=null)
	{
		return $this->fetchAll($sql,$key);
	}
	/**
     * Object方式返回所有行
     * @param string $sql
     * @param string $key 以该键值为下标返回
     */
	public function &loadObjList($sql,$key=null){
		return $this->fetchAll($sql,$key,PDO::FETCH_OBJ);
	}
	/**
	* 返回一行
	*
	* return array
	*/
	function &loadAssoc($sql)
	{
		return $this->fetch($sql);
	}
	function &loadObj($sql,$fetchMode=PDO::FETCH_ASSOC)
	{
		return $this->fetch($sql,PDO::FETCH_OBJ);
	}
	/**
	 * 支持多条记录同时更新
	 * 
	 */
	function updateList($table,$array,$primaryKey='id'){
		if(!is_array($array) || !$table)return false;
		$flag=true;
		foreach($array as $value){
			if(!is_array($value))continue;
			if(!$this->update($table,$value,$primaryKey)){
				$flag=false;
				break;
			}
		}
		return $flag;
	}
	/**
	 * 更新关联的表
	 */
	function update($table, $array,$primaryKey='id') {
		if(!$array || !$table || !is_array($array)){return false;} 
		if(is_string($primaryKey) && $primaryKey){
			if(strpos($primaryKey,',')!==false){
				$tjarr=explode(',',$primaryKey);
				foreach($tjarr as &$value){
					if(isset($array[$value])){
						$value="$value='".$array[$value]."' ";
					}
				}
				$tj='where '.implode(' and ',$tjarr);
			}else
			$tj = 'where `'.$primaryKey.'`=' . $array[$primaryKey];
		}else{
			$this->showErrorInfo('Primary Key Error!');
			return false;
		} 
		$setdata = '';
		foreach ($array as $key => $value) { 
			$error=false;
			if (is_array($value)) {
				$this->showErrorInfo('Update Value Error!');
				return false;
			} else {
				if($value==='now()'){
					$setdata .= "`$key`=$value,";
				}else{
					$setdata .= "`$key`='$value',";
				}
			}
		}
		$strQuery = 'update `#__'. $table . '` set ' . substr($setdata, 0, -1).' '. $tj;
		$rs=$this->query($strQuery);
		return !$error && $rs;
	}
	/**
	 * 插入表记录(批量)
	 */
	function insertList($table,$array ){
		if(!is_array($array) || !$table)return false;
		$flag=true;
		foreach($array as $value){
			if(!is_array($value))continue;
			if(!$this->insert($table,$value)){
				$flag=false;
				break;
			}
		}
		return $flag;
	}
	/**
	 * 插入表记录
	 */
	function insert($table, $array ){ 
		if (!$array || !is_array($array)) return false;
		$fields = '(';
		$values = '(';
		foreach ($array as $key => $value) {
			if (is_array($value)) {
				$this->insert_assoc[$key] = $value;
			} else {
				$fields .= '`' . $key . '`,';
				if ($value === 'now()'){
					$values .= 'now(),';
				}else{
					$values .= '\'' . $value . '\',';
				}
			}
		}
		$fields = substr($fields, 0, -1) . ')';
		$values = substr($values, 0, -1) . ')';
		$strQuery = 'insert into `#__' . $table . '` ' . $fields . ' values ' . $values;
		if($this->query($strQuery) === false ){
			return false;
		}else{
			return $this->lastInsertid();
		}
	}
	/**
	 * 引用一个标识符名(field, table, etc)
	 *
	 * @access public
	 * @param string 标识符名
	 * @return string 被引用后的值
	 */
	function nameQuote($s,$q=null)
	{
		if($q==null)
		{
			$q=$this->nameQuote;
		}
		if (!isset($q{1}))
		{
			return $q . $s . $q;
		} else {
			return $q{0} . $s . $q{1};
		}
	}
}//PDO class

class DBStatement extends PDOStatement
{
	protected $pdo;
	protected function __construct($pdo)
	{
		$this->pdo = $pdo;
		$this->setFetchMode(PDO :: FETCH_OBJ);
	}
	/**
	 * 用数组做为参数进行绑定
	 * $stmt = $dbh->prepare("INSERT INTO tExample (id,value) VALUES (:id,:value)");
	 * $test = array('id' => '1','value' => '2');
	 * $stmt->bindValueArr($test);
	 * 
	 * @param array $parameter
	 * @return boolean 
	 */
	function bindValueArr($parameter)
	{
		foreach ($parameter as $k=>$v)
		{
			$this->bindValue(':'.$k,$v);
		}
		return true;
	}
	/**
	 * 返回第一行
	 */
	public function fetchFirst()
	{
		$row = $this->fetch(PDO :: FETCH_NUM);
		return $row[0];
	}
	// real cast number
	public function fetch($fetch_style = null, $cursor_orientation = null, $cursor_offset = null)
	{
		$row = parent :: fetch($fetch_style, $cursor_orientation, $cursor_offset);
		/*if (is_array($row))
			foreach ($row as $key => $value)
				if (strval(intval($value)) === $value)
					$row[$key] = intval($value);
		elseif (strval(floatval($value)) === $value) $row[$key] = floatval($value);*/
		return $row;
	}

	/**
	 * 执行一个statment
	 * @param mix $args 可变参数,可以为数组execute($arr)，也可以为execute($a,$b,$c);
	 * 
	 */
	public function execute($args = null)
	{
		$args2 = func_get_args();
		if (is_array($args))
		{
			if(isset($args[0]))
			{
				return parent :: execute($args);
			}else{
				$this->bindValueArr($args);
				return parent :: execute();
			}
		}
		else
		{
			$args = func_get_args();
			if($args!=array())
			{
				return parent::execute($args);
			}else{
				return parent::execute();
			}
		}
	}
	public function last_query()
	{
		return $this->pdo->last_query();
	}

}
?>